#include "file.h"

#define BITMAP_SIZE 0x1000
#define BITMAP_SECTOR_START 0x200
#define BITMAP_SECTOR_COUNT (BITMAP_SIZE >> 9)
#define FILE_ADDRESS_MAX 0x6A
#define ALLOCATE_SECTOR_START (BITMAP_SECTOR_START + BITMAP_SECTOR_COUNT)

STATIC BYTE FsBitmap[BITMAP_SIZE];
STATIC DWORD FsBitmapSize;
STATIC INODE FsRootNode;
STATIC SUPER_BLOCK FsSuperBlock;

STATIC DWORD dwAllocateIndex;

STATIC DWORD FsGetSectorCount(DWORD dwLength) {
    if(dwLength == 0)
        return 0;
    return ((dwLength - 1) >> 9) + 1;
}

VOID FsSuperBlockInitialize(VOID) {
    DevHardDiskRead(0, SUPER_BLOCK_LOCATION, 1, &FsSuperBlock);
    if (FsSuperBlock.dwMagic != SUPER_BLOCK_MAGIC) {
        FsSuperBlock.dwMagic = SUPER_BLOCK_MAGIC;
        FsSuperBlock.dwRoot = FsAllocateSector();
        {
            DWORD dwLow, dwHigh;
            dwLow = FsSuperBlock.dwRoot;
            dwHigh = BITMAP_SIZE << 3;
            while (dwLow < dwHigh) {
                DWORD dwMid = (dwLow + dwHigh) >> 1;
                if (DevHardDiskExisted(0, dwMid)) {
                    dwLow = dwMid + 1;
                } else {
                    dwHigh = dwMid;
                }
            }
            FsSuperBlock.dwSectors = dwLow;
            INODE tempNode;
            RtlZeroMemory(&tempNode, sizeof(INODE));
            RtlStringCopy(tempNode.lpcName, "root");
            tempNode.dwType = FILE_TYPE_FOLDER;
            tempNode.dwAttribute |= FILE_ATTR_KERNEL;
            tempNode.dwMagic = FILE_INODE_MAGIC;
            DevHardDiskWrite(0, FsSuperBlock.dwRoot, 1, &tempNode);
        }
        DevHardDiskWrite(0, SUPER_BLOCK_LOCATION, 1, &FsSuperBlock);
    }
}

VOID FsInitialize(VOID) {
    DevHardDiskRead(0, SUPER_BLOCK_LOCATION, 1, &FsSuperBlock);
    if (FsSuperBlock.dwMagic != SUPER_BLOCK_MAGIC) {
        RtlZeroMemory(FsBitmap, BITMAP_SIZE);
        DevHardDiskWrite(0, BITMAP_SECTOR_START, BITMAP_SECTOR_COUNT, FsBitmap);
        FsSuperBlockInitialize();
    } else {
        DevHardDiskRead(0, FsSuperBlock.dwRoot, 1, &FsRootNode);
        DevHardDiskRead(0, BITMAP_SECTOR_START, BITMAP_SECTOR_COUNT, FsBitmap);
    }
    FsBitmapSize = FsSuperBlock.dwSectors >> 5;
    if (FsBitmapSize > BITMAP_SIZE) {
        FsBitmapSize = BITMAP_SIZE;
    }
    dwAllocateIndex = 0;
}

DWORD FsGetRootNode(VOID) {
    return FsSuperBlock.dwRoot;
}

DWORD FsAllocateSector(VOID) {
    REG DWORD i = dwAllocateIndex;
    do {
        if (FsBitmap[i] != 0xFF) {
            REG DWORD j;
            dwAllocateIndex = i;
            for (j = 0; j < 8; j++)
                if (((FsBitmap[i] >> j) & 1) == 0) {
                    FsBitmap[i] |= (1 << j);
                    DevHardDiskWrite(0, BITMAP_SECTOR_START + (i >> 9), 1, FsBitmap + (i & 0xFFFFFE00));
                    return ALLOCATE_SECTOR_START + (i << 3) + j;
                }
        }
        ++i;
        if (i >= FsBitmapSize)
        i = 0;
    } while (i != dwAllocateIndex);
    return NULL;
}

VOID FsAllocateSectors(BOOLEAN (*lpfnCallback)(DWORD_PTR)) {
    REG DWORD i = dwAllocateIndex;
    do {
        if (FsBitmap[i] != 0xFF) {
            REG DWORD j;
            for (j = 0; j < 8; j++)
                if (((FsBitmap[i] >> j) & 1) == 0) {
                    FsBitmap[i] |= (1 << j);
                    if (!lpfnCallback(ALLOCATE_SECTOR_START + (i << 3) + j)) {
                        dwAllocateIndex = i;
                        DevHardDiskWrite(0, BITMAP_SECTOR_START + (i >> 9), 1, FsBitmap + (i & 0xFFFFFE00));
                        return;
                    }
                }
            DevHardDiskWrite(0, BITMAP_SECTOR_START + (i >> 9), 1, FsBitmap + (i & 0xFFFFFE00));
        }
        ++i;
        if (i >= FsBitmapSize)
            i = 0;
    } while (i != dwAllocateIndex);
    lpfnCallback(NULL);
}

VOID FsFreeSector(DWORD dwAddress) {
    REG DWORD dwIndex = (dwAddress - ALLOCATE_SECTOR_START) >> 3;
    FsBitmap[dwIndex] ^= (1 << (dwAddress & 0x7));
    DevHardDiskWrite(0, BITMAP_SECTOR_START + (dwIndex >> 9), 1, FsBitmap + (dwIndex & 0xFFFFFE00));
}

DWORD FsCreateFile(DWORD dwBase, LPCSTR lpszFilename) {
    INODE Base;
    REG DWORD i;
    REG DWORD Sector;
    INODE FileNode;
    DevHardDiskRead(0, dwBase, 1, &Base);
    if (Base.dwMagic != FILE_INODE_MAGIC || Base.dwType != FILE_TYPE_FOLDER ||
        Base.dwLength >= FILE_ADDRESS_MAX) {
        return NULL;
    }
    for (i = 0; i < Base.dwLength; i++) {
        DevHardDiskRead(0, Base.dwAddress[i], 1, &FileNode);
        if (!RtlStringNCompare(lpszFilename, FileNode.lpcName, 0x40)) {
            return NULL;
        }
    }
    RtlZeroMemory(&FileNode, sizeof(INODE));
    RtlCopyMemory(FileNode.lpcName, (LPCVOID)lpszFilename, 0x40);
    FileNode.dwMagic = FILE_INODE_MAGIC;
    FileNode.dwType = FILE_TYPE_DEFAULT;
    FileNode.dwSuper = dwBase;
    Sector = FsAllocateSector();
    Base.dwAddress[Base.dwLength++] = Sector;
    DevHardDiskWrite(0, dwBase, 1, &Base);
    DevHardDiskWrite(0, Sector, 1, &FileNode);
    return Sector;
}

DWORD FsOpenFile(DWORD dwBase, LPCSTR lpszFilename) {
    INODE Base;
    REG DWORD i;
    INODE FileNode;
    DevHardDiskRead(0, dwBase, 1, &Base);
    if (Base.dwMagic != FILE_INODE_MAGIC || Base.dwType != FILE_TYPE_FOLDER) {
        return NULL;
    }
    for (i = 0; i < Base.dwLength; i++) {
        DevHardDiskRead(0, Base.dwAddress[i], 1, &FileNode);
        if (!RtlStringNCompare(lpszFilename, FileNode.lpcName, 0x40)) {
            if (FileNode.dwType != FILE_TYPE_DEFAULT) {
                return NULL;
            }
            return Base.dwAddress[i];
        }
    }
    return NULL;
}

VOID FsDeleteFile(DWORD dwBase, LPCSTR lpszFilename) {
    INODE Base;
    REG DWORD i;
    INODE FileNode;
    DevHardDiskRead(0, dwBase, 1, &Base);
    if (Base.dwMagic != FILE_INODE_MAGIC || Base.dwType != FILE_TYPE_FOLDER) {
        return;
    }
    for (i = 0; i < Base.dwLength; i++) {
        DevHardDiskRead(0, Base.dwAddress[i], 1, &FileNode);
        if (!RtlStringNCompare(lpszFilename, FileNode.lpcName, 0x40)) {
            if (FileNode.dwType != FILE_TYPE_DEFAULT) {
                return;
            }
            Base.dwAddress[i] = Base.dwAddress[--Base.dwLength];
            FsFreeSector(Base.dwAddress[i]);
            for (i = 0; i < FileNode.dwLength; i++) {
                FsFreeSector(FileNode.dwAddress[i]);
            }
            DevHardDiskWrite(0, dwBase, 1, &Base);
            return;
        }
    }
}

VOID FsGetFileName(DWORD dwFile, LPSTR lpszBuffer){
    INODE FileNode;
    DevHardDiskRead(0, dwFile, 1, &FileNode);
    if(FileNode.dwMagic != FILE_INODE_MAGIC)
        return;
    RtlStringNCopy(lpszBuffer, FileNode.lpcName, 0x40);
    lpszBuffer[0x40] = '\0';
}

BOOL FsGetFileAttributes(DWORD dwFile, LPDWORD lpdwLength, LPDWORD lpdwAttributes, LPDWORD lpdwSuper, LPDWORD lpdwType){
    INODE File;
    DevHardDiskRead(0, dwFile, 1, &File);
    if(File.dwMagic != FILE_INODE_MAGIC)
        return FALSE;
    if(lpdwLength)
        *lpdwLength = File.dwLength;
    if(lpdwAttributes)
        *lpdwAttributes = File.dwAttribute;
    if(lpdwSuper)
        *lpdwSuper = File.dwSuper;
    if(lpdwType)
        *lpdwType = File.dwType;
    return TRUE;
}

VOID FsReadFile(DWORD dwFile, DWORD dwStart, LPVOID lpvBuffer, DWORD dwLength, LPDWORD dwBytesOfRead) {
    INODE File;
    REG DWORD i;
    BYTE bBuffer[0x200];
    DWORD dwEnd = dwStart + dwLength;
    DWORD dwStartAddr = dwStart >> 9;
    DWORD dwEndAddr;
    DWORD dwCur = dwStart;
    DevHardDiskRead(0, dwFile, 1, &File);
    if (File.dwMagic != FILE_INODE_MAGIC || File.dwType != FILE_TYPE_DEFAULT) {
        if (dwBytesOfRead)
            *dwBytesOfRead = 0;
        return;
    }
    if (dwStart >= File.dwLength) {
        if (dwBytesOfRead)
            *dwBytesOfRead = 0;
        return;
    }
    if (dwEnd > File.dwLength) {
        dwEnd = File.dwLength;
    }
    if (dwEnd == 0) {
        dwEnd = 1;
    }
    dwEndAddr = (dwEnd - 1) >> 9;
    for (i = dwStartAddr; i <= dwEndAddr; i++) {
        DevHardDiskRead(0, File.dwAddress[i], 1, bBuffer);
        DWORD dwNext = ((i + 1) << 9);
        if (dwNext > dwEnd) {
            dwNext = dwEnd;
        }
        RtlCopyMemory((LPBYTE)lpvBuffer + dwCur - dwStart, (LPCVOID)(bBuffer + dwCur - (i << 9)), dwNext - dwCur);
        if (dwBytesOfRead)
            *dwBytesOfRead += dwNext - dwCur;
        dwCur = dwNext;
    }
}

VOID FsWriteFile(DWORD dwFile, DWORD dwStart, LPCVOID lpvBuffer, DWORD dwLength, LPDWORD dwBytesOfWrite) {
    INODE File;
    REG DWORD i;
    BYTE bBuffer[0x200];
    DWORD dwEnd = dwStart + dwLength;
    DWORD dwStartAddr = dwStart >> 9;
    DWORD dwEndAddr;
    DWORD dwCur = dwStart;
    DevHardDiskRead(0, dwFile, 1, &File);
    if (File.dwMagic != FILE_INODE_MAGIC || File.dwType != FILE_TYPE_DEFAULT) {
        if (dwBytesOfWrite)
            *dwBytesOfWrite = 0;
        return;
    }
    if (dwEnd > File.dwLength) {
        DWORD dwCnt1, dwCnt2;
        dwCnt1 = FsGetSectorCount(File.dwLength);
        if (dwEnd >= 0x6A * 0x200) {
            dwEnd = 0x6A * 0x200;
        }
        File.dwLength = dwEnd;
        dwCnt2 = FsGetSectorCount(dwEnd);
        for (; dwCnt1 < dwCnt2; dwCnt1++) {
            File.dwAddress[dwCnt1] = FsAllocateSector();
        }
    }
    if (dwEnd == 0) {
        dwEnd = 1;
    }
    dwEndAddr = (dwEnd - 1) >> 9;
    for (i = dwStartAddr; i <= dwEndAddr; i++) {
        DWORD dwNext = ((i + 1) << 9);
        if (dwNext > dwEnd) {
            dwNext = dwEnd;
        }
        if (dwNext - dwCur != 0x200) {
            DevHardDiskRead(0, File.dwAddress[i], 1, bBuffer);
        }
        RtlCopyMemory(bBuffer + dwCur - (i << 9), (LPCVOID)((LPBYTE)lpvBuffer + dwCur - dwStart), dwNext - dwCur);
        DevHardDiskWrite(0, File.dwAddress[i], 1, bBuffer);
        if (dwBytesOfWrite)
            *dwBytesOfWrite += dwNext - dwCur;
        dwCur = dwNext;
    }
    DevHardDiskWrite(0, dwFile, 1, &File);
}

DWORD FsCreateDirectory(DWORD dwBase, LPCSTR lpszDirname) {
    INODE Base;
    REG DWORD i;
    REG DWORD Sector;
    INODE FileNode;
    DevHardDiskRead(0, dwBase, 1, &Base);
    if (Base.dwType != FILE_TYPE_FOLDER || Base.dwLength >= FILE_ADDRESS_MAX ||
            Base.dwMagic != FILE_INODE_MAGIC) {
        return NULL;
    }
    for (i = 0; i < Base.dwLength; i++) {
        DevHardDiskRead(0, Base.dwAddress[i], 1, &FileNode);
        if (!RtlStringNCompare(Base.lpcName, FileNode.lpcName, 0x40)) {
            return NULL;
        }
    }
    Sector = FsAllocateSector();
    Base.dwAddress[Base.dwLength++] = Sector;
    RtlZeroMemory(&FileNode, sizeof(INODE));
    RtlCopyMemory(FileNode.lpcName, (LPCVOID)lpszDirname, 0x40);
    FileNode.dwMagic = FILE_INODE_MAGIC;
    FileNode.dwType = FILE_TYPE_FOLDER;
    FileNode.dwSuper = dwBase;
    DevHardDiskWrite(0, Sector, 1, &FileNode);
    DevHardDiskWrite(0, dwBase, 1, &Base);
    return Sector;
}

DWORD FsOpenDirectory(DWORD dwBase, LPCSTR lpszDirname) {
    INODE Base;
    REG DWORD i;
    INODE FileNode;
    DevHardDiskRead(0, dwBase, 1, &Base);
    if (Base.dwType != FILE_TYPE_FOLDER) {
        return NULL;
    }
    for (i = 0; i < Base.dwLength; i++) {
        DevHardDiskRead(0, Base.dwAddress[i], 1, &FileNode);
        if (!RtlStringNCompare(lpszDirname, FileNode.lpcName, 0x40)) {
            if (FileNode.dwType != FILE_TYPE_FOLDER) {
                return NULL;
            }
            return Base.dwAddress[i];
        }
    }
    return NULL;
}

VOID FsDeleteDirectory(DWORD dwBase, LPCSTR lpszDirname) {
    INODE Base;
    REG DWORD i;
    INODE FileNode;
    DevHardDiskRead(0, dwBase, 1, &Base);
    if (Base.dwType != FILE_TYPE_FOLDER) {
        return;
    }
    for (i = 0; i < Base.dwLength; i++) {
        DevHardDiskRead(0, Base.dwAddress[i], 1, &FileNode);
        if (!RtlStringNCompare(lpszDirname, FileNode.lpcName, 0x40)) {
            if (FileNode.dwType != FILE_TYPE_FOLDER) {
                return;
            }
            Base.dwAddress[i] = Base.dwAddress[--Base.dwLength];
            FsFreeSector(Base.dwAddress[i]);
            for (i = 0; i < FileNode.dwLength; i++) {
                FsFreeSector(FileNode.dwAddress[i]);
            }
            DevHardDiskWrite(0, dwBase, 1, &Base);
            return;
        }
    }
}

VOID FsFindAllFiles(DWORD dwBase, VOID (*lpfnCallback)(DWORD)) {
    INODE Base;
    REG DWORD i;
    DevHardDiskRead(0, dwBase, 1, &Base);
    if (Base.dwMagic != FILE_INODE_MAGIC || Base.dwType != FILE_TYPE_FOLDER) {
        return;
    }
    for (i = 0; i < Base.dwLength; i++) {
        lpfnCallback(Base.dwAddress[i]);
    }
    lpfnCallback(NULL);
}
